//******************************************************************
//
// Copyright 2014 Intel Corporation.
//
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
#include <signal.h>
#include <thread>
#include <functional>
#include "server.h"
#include "sensors.h"
#include "namedefs.h"
using namespace Sensors;

BTN::BTN(shared_ptr<OCResource> Resource)
{
    m_isObserved = false;
    m_resourceHandle = Resource;
    m_GETCallback = bind(&BTN::onGet, this, placeholders::_1, placeholders::_2,
                         placeholders::_3);
    m_OBSERVECallback = bind(&BTN::onObserve, this, placeholders::_1, placeholders::_2,
                             placeholders::_3, placeholders::_4);
    m_isLedBright = false;
    m_lastValue = 1;
}

BTN::~BTN()
{

}

void BTN::startObserve()
{
    if (!m_isObserved)
    {
        cout << "Started observing button sensor" << endl;
        m_resourceHandle->observe(ObserveType::Observe, QueryParamsMap(), m_OBSERVECallback);
    }
    m_isObserved = true;
}

void BTN::stopObserve()
{
    if (m_isObserved)
    {
        m_resourceHandle->cancelObserve();
        cout << "Stopped observing button sensor" << endl;
    }
    m_isObserved = false;
}

void BTN::onObserve(const HeaderOptions headerOptions, const OCRepresentation& rep,
                             int eCode, int sequenceNumber)
{
    if (eCode == OC_STACK_OK)
    {
        int value;
        rep.getValue(BTN_RESOURCE_KEY, value);
        if(value == 0 && m_lastValue == 1)
        {
			//it is the first zero => changing state
			m_isLedBright = !m_isLedBright;
			SetOnboardLed(m_isLedBright);
		}
		m_lastValue = value;
        //cout << "Observing Button: Current button reading is " << value << endl;
        //cout << "Sequence number: " << sequenceNumber << endl;
    }
    else
    {
        cerr << "BtnSensor: Observer response error" << endl;
    }
}

void BTN::get()
{
    QueryParamsMap params;
    m_resourceHandle->get(params, m_GETCallback);
}

void BTN::onGet(const HeaderOptions& headerOptions, const OCRepresentation& representation,
                         int errCode)
{
    if (errCode == OC_STACK_OK)
    {
        int value;
        representation.getValue(BTN_RESOURCE_KEY, value);
        cout << endl << endl << "Current button sensor reading: " << value << endl;
        //to do : change m_isLedBright
    }
    else {
        cerr << endl << endl << "Error in GET response from button sensor resource" << endl;
    }
}

void IoTServer::initializePlatform()
{
    cout << "Running IoTServer constructor" << endl;
    m_platformConfig = make_shared<PlatformConfig>(ServiceType::InProc, ModeType::Both, "0.0.0.0",
                                                   0, OC::QualityOfService::HighQos);
    OCPlatform::Configure(*m_platformConfig);
    m_resourceDiscoveryCallback = bind(&IoTServer::discoveredResource, this, placeholders::_1);
}

IoTServer::IoTServer()
{
    initializePlatform();
    setupResources();
    m_ambientLightRepresentation.setValue(LIGHT_RESOURCE_KEY, 0);
    m_ledRepresentation.setValue(LED_RESOURCE_KEY, 0);
    SetupPins();
}

IoTServer::~IoTServer()
{
    cout << "Running IoTServer destructor" << endl;
    ClosePins();
}

void IoTServer::setupResources()
{
    EntityHandler cb2 = bind(&IoTServer::lightEntityHandler, this, placeholders::_1);
    createResource(LIGHT_RESOURCE_ENDPOINT, LIGHT_RESOURCE_TYPE, cb2, m_ambientLightResource);
    IoTObserverCb lightObsCb = bind(&IoTServer::lightObserverLoop, this);
    m_ambientLightObserverLoop = make_shared<IoTObserver>(lightObsCb);

    EntityHandler cb3 = bind(&IoTServer::LEDEntityHandler, this, placeholders::_1);
    createResource(LED_RESOURCE_ENDPOINT, LED_RESOURCE_TYPE, cb3, m_ledResource);
}

void IoTServer::createResource(string Uri, string Type, EntityHandler Cb, OCResourceHandle& Handle)
{
    string resourceUri = Uri;
    string resourceType = Type;
    string resourceInterface = EDISON_RESOURCE_INTERFACE;
    uint8_t resourceFlag = OC_DISCOVERABLE | OC_OBSERVABLE;

    OCStackResult result = OCPlatform::registerResource(Handle, resourceUri, resourceType,
                                                        resourceInterface, Cb, resourceFlag);

    if (result != OC_STACK_OK)
        cerr << "Could not create " << Type << " resource" << endl;
    else
        cout << "Successfully created " << Type << " resource" << endl;
}

void IoTServer::putLEDRepresentation()
{
    int state = 0;
    m_ledRepresentation.getValue(LED_RESOURCE_KEY, state);
    SetOnboardLed(state);
    if (state == 0)
        cout << "Turned off LED" << endl;
    else if (state == 1)
        cout << "Turned on LED" << endl;
    else
        cerr << "Invalid request value" << endl;
}

OCRepresentation IoTServer::getLEDRepresentation()
{
    return m_ledRepresentation;
}

void IoTServer::lightObserverLoop()
{
    usleep(1500000);
    cout << "Light Observer Callback" << endl;
    shared_ptr<OCResourceResponse> resourceResponse(new OCResourceResponse());
    resourceResponse->setErrorCode(200);
    resourceResponse->setResourceRepresentation(getLightRepresentation(),
    EDISON_RESOURCE_INTERFACE);
    OCStackResult result = OCPlatform::notifyListOfObservers(m_ambientLightResource,
                                                             m_ambientLightObservers,
                                                             resourceResponse);
    if (result == OC_STACK_NO_OBSERVERS)
    {
        cout << "No more observers..Stopping observer loop..." << endl;
        m_ambientLightObserverLoop->stop();
    }
}

OCRepresentation IoTServer::getLightRepresentation()
{
    m_ambientLightRepresentation.setValue(LIGHT_RESOURCE_KEY, GetLightLevel());
    return m_ambientLightRepresentation;
}

OCEntityHandlerResult IoTServer::lightEntityHandler(shared_ptr<OCResourceRequest> Request)
{
    OCEntityHandlerResult result = OC_EH_ERROR;
    if (Request)
    {
        string requestType = Request->getRequestType();
        int requestFlag = Request->getRequestHandlerFlag();
        if (requestFlag & RequestHandlerFlag::RequestFlag)
        {
            auto Response = std::make_shared<OC::OCResourceResponse>();
            Response->setRequestHandle(Request->getRequestHandle());
            Response->setResourceHandle(Request->getResourceHandle());
            if (requestType == "GET")
            {
                cout << "GET request for ambient light reading" << endl;
                if (Response)
                {
                    Response->setErrorCode(200);
                    Response->setResponseResult(OC_EH_OK);
                    Response->setResourceRepresentation(getLightRepresentation());
                    if (OCPlatform::sendResponse(Response) == OC_STACK_OK)
                    {
                        result = OC_EH_OK;
                    }
                }
            }
            else
            {
                Response->setResponseResult(OC_EH_ERROR);
                OCPlatform::sendResponse(Response);
                cerr << "Unsupported request type" << endl;
                return result;
            }
        }
        if (requestFlag & RequestHandlerFlag::ObserverFlag)
        {
            ObservationInfo observationInfo = Request->getObservationInfo();
            if (ObserveAction::ObserveRegister == observationInfo.action)
            {
                cout << "Starting observer for ambient light sensor" << endl;
                m_ambientLightObservers.push_back(observationInfo.obsId);
                m_ambientLightObserverLoop->start();
            }
            else if (ObserveAction::ObserveUnregister == observationInfo.action)
            {
                m_ambientLightObservers.erase(
                        remove(m_ambientLightObservers.begin(), m_ambientLightObservers.end(),
                               observationInfo.obsId),
                        m_ambientLightObservers.end());
            }
        }
    }
    return result;
}

OCEntityHandlerResult IoTServer::LEDEntityHandler(shared_ptr<OCResourceRequest> Request)
{
    OCEntityHandlerResult result = OC_EH_ERROR;
    if (Request)
    {
        string requestType = Request->getRequestType();
        int requestFlag = Request->getRequestHandlerFlag();
        if (requestFlag & RequestHandlerFlag::RequestFlag)
        {
            auto Response = std::make_shared<OC::OCResourceResponse>();
            Response->setRequestHandle(Request->getRequestHandle());
            Response->setResourceHandle(Request->getResourceHandle());
            if (requestType == "PUT")
            {
                cout << "PUT request for platform LED" << endl;
                OCRepresentation requestRep = Request->getResourceRepresentation();
                if (requestRep.hasAttribute(LED_RESOURCE_KEY))
                {
                    try
                    {
                        requestRep.getValue<int>(LED_RESOURCE_KEY);
                    }
                    catch (...)
                    {
                        Response->setResponseResult(OC_EH_ERROR);
                        OCPlatform::sendResponse(Response);
                        cerr << "Client sent invalid resource value type" << endl;
                        return result;
                    }
                }
                else
                {
                    Response->setResponseResult(OC_EH_ERROR);
                    OCPlatform::sendResponse(Response);
                    cerr << "Client sent invalid resource key" << endl;
                    return result;
                }
                m_ledRepresentation = requestRep;
                putLEDRepresentation();
                if (Response)
                {
                    Response->setErrorCode(200);
                    Response->setResourceRepresentation(getLEDRepresentation());
                    Response->setResponseResult(OC_EH_OK);
                    if (OCPlatform::sendResponse(Response) == OC_STACK_OK)
                    {
                        result = OC_EH_OK;
                    }
                }
            }
            else if (requestType == "GET")
            {
                cout << "GET request for platform LED" << endl;
                if (Response)
                {
                    Response->setErrorCode(200);
                    Response->setResourceRepresentation(getLEDRepresentation());
                    Response->setResponseResult(OC_EH_OK);
                    if (OCPlatform::sendResponse(Response) == OC_STACK_OK)
                    {
                        result = OC_EH_OK;
                    }
                }
            }
            else
            {
                Response->setResponseResult(OC_EH_ERROR);
                OCPlatform::sendResponse(Response);
                cerr << "Unsupported request type" << endl;
            }
        }
    }
    return result;
}

shared_ptr<BTN> IoTServer::getBTNSensor()
{
	return m_btnSensor;
}

void IoTServer::findResource()
{
	string coap_multicast_discovery = string(OC_WELL_KNOWN_QUERY "?if=" EDISON_RESOURCE_INTERFACE);
    OCPlatform::findResource("", coap_multicast_discovery.c_str(), m_resourceDiscoveryCallback,
                             OC::QualityOfService::LowQos);
}

void IoTServer::discoveredResource(shared_ptr<OCResource> Resource)
{
	 try
    {
        if (Resource)
        {
            string resourceUri = Resource->uri();
            string hostAddress = Resource->host();

            cout << "\nFound Resource" << endl << "Resource Types:" << endl;
            for (auto& resourceTypes : Resource->getResourceTypes())
            {
                cout << "\t" << resourceTypes << endl;
            }

            cout << "Resource Interfaces: " << endl;
            for (auto& resourceInterfaces : Resource->getResourceInterfaces())
            {
                cout << "\t" << resourceInterfaces << endl;
            }
            cout << "Resource uri: " << resourceUri << endl;
            cout << "host: " << hostAddress << endl;

            if (resourceUri == BTN_RESOURCE_ENDPOINT)
            {
                m_btnSensor = make_shared<BTN>(Resource);
                m_btnSensor->startObserve();
            }
        }
    }
    catch (OCException& ex)
    {
        cerr << "Caught exception in discoveredResource: " << ex.reason() << endl;
    }
}

int quit = 0;

void handle_signal(int signal)
{
    quit = 1;
}

int main()
{
    struct sigaction sa;
    sigfillset(&sa.sa_mask);
    sa.sa_flags = 0;
    sa.sa_handler = handle_signal;
    sigaction(SIGINT, &sa, NULL);
    cout << "Press Ctrl-C to quit...." << endl;
    IoTServer server;
    server.findResource();
    pause();
    
    //stop observe
    if(server.getBTNSensor())
    	server.getBTNSensor()->stopObserve();
    
    return 0;
}

